//===============================================================================
// Q4Tech Engineering Team
// Mobile Updater Application Block - November 2006
//===============================================================================
// Copyright  Q4Tech Informatica Argentina.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.Runtime.InteropServices;

namespace Q4Tech.Engineering.Ipc
{
    public class PointToPointMessageQueue : IDisposable, IPointToPointMessageQueue
    {
        public enum AccessMode
        {
            Read,
            Write
        }

        private IntPtr queueHandle = INVALID_HANDLE_VALUE;
        private String name;
        private int messageSize;
        private MsgQueueInfo queueInfo = new MsgQueueInfo();
        private AccessMode accessMode;

        public String Name
        {
            get { return name; }
        }

        public void Initialize(String name, AccessMode accessMode, int maxMessageSize, int maxMessageCount)
        {
            if (String.IsNullOrEmpty(name))
                throw new ArgumentException();

            if (!String.IsNullOrEmpty(this.name))
                throw new InvalidOperationException();

            this.accessMode = accessMode;
            this.name = name;

            bool readAccess = accessMode == AccessMode.Read;

            queueHandle = CreateMsgQueue(this.name, new MsgQueueOptions(readAccess, maxMessageCount, maxMessageSize));

            if (queueHandle == (IntPtr)NULL)
            {
                throw new InvalidOperationException();
            }
        }

        public void Enqueue(byte[] buffer)
        {
            if (!WriteMsgQueue(queueHandle, buffer, buffer.Length, 0, 0))
            {
                throw new InvalidOperationException();
            }
        }

        /// <summary>
        /// Dequeues an item
        /// Blocks until an item is available or specified time elapses
        /// </summary>
        /// <returns></returns>
        public byte[] Dequeue(uint timeout)
        {
            if (WaitForSingleObject(queueHandle, timeout) == WAIT_OBJECT_0)
                return Dequeue();
            else
                return null;
        }

        /// <summary>
        /// Empties the queue for any possible messages pending for reading
        /// </summary>
        public void Purge()
        {
            byte[] buffer = new byte[messageSize];
            int bytesRead, flags;

            while (ReadMsgQueue(queueHandle, buffer, messageSize, out bytesRead, 0, out flags)) ;
        }

        /// <summary>
        /// Dequeues an item
        /// Blocks until an item is available
        /// </summary>
        /// <returns></returns>
        public byte[] Dequeue()
        {
            if (messageSize == 0)
                GetQueueInfo();

            byte[] buffer = new byte[messageSize];

            int bytesRead, flags;

            if (!ReadMsgQueue(queueHandle, buffer, buffer.Length, out bytesRead, TIMEOUT_INFINITE, out flags))
            {
                throw new InvalidOperationException();
            }
            
            return buffer;
        }

        /// <summary>
        /// Dequeues an item
        /// Blocks until an item is available
        /// </summary>
        /// <returns></returns>
        public int Dequeue(ref byte[] buffer)
        {
            int bytesRead, flags;

            if (!ReadMsgQueue(queueHandle, buffer, buffer.Length, out bytesRead, TIMEOUT_INFINITE, out flags))
                return 0;
            else
                return bytesRead;
        }

        private void GetQueueInfo()
        {
            queueInfo.dwSize = 28;
            GetMsgQueueInfo(queueHandle, ref queueInfo);
            messageSize = queueInfo.cbMaxMessage;
        }

        public bool RemotePeerPresent
        {
            get
            {
                GetQueueInfo();

                return accessMode == AccessMode.Read && queueInfo.wNumWriters > 0 ||
                       accessMode == AccessMode.Write && queueInfo.wNumReaders > 0;
            }
        }

        public int Count
        {
            get
            {
                if (queueHandle == INVALID_HANDLE_VALUE)
                    throw new InvalidOperationException();

                GetQueueInfo();

                return queueInfo.dwCurrentMessages;
            }
        }

        public void Close()
        {
            if (queueHandle != INVALID_HANDLE_VALUE)
            {
                CloseMsgQueue(queueHandle);
                queueHandle = INVALID_HANDLE_VALUE;
            }
        }

        #region API declarations

        private static IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1);
        private const uint TIMEOUT_INFINITE = 0xFFFFFFFF;
        private const int ERROR_INSUFFICIENT_BUFFER = 122;
        private const int WAIT_OBJECT_0 = 0x00000000;
        private const int NULL = 0;

        [DllImport("coredll.dll", SetLastError = true)]
        private static extern IntPtr CreateMsgQueue(string lpszName, MsgQueueOptions lpOptions);

        [DllImport("coredll.dll", SetLastError = true)]
        private static extern bool GetMsgQueueInfo(IntPtr hMsgQ, ref MsgQueueInfo lpInfo);

        [DllImport("coredll.dll", SetLastError = true)]
        private static extern IntPtr OpenMsgQueue(IntPtr hSrcProc, IntPtr hMsgQ, MsgQueueOptions lpOptions);

        [DllImport("coredll.dll", SetLastError = true)]
        private static extern bool ReadMsgQueue(IntPtr hMsgQ, byte[] lpBuffer, int cbBufferSize, out int lpNumberOfBytesRead, uint dwTimeout, out int pdwFlags);

        [DllImport("coredll.dll", SetLastError = true)]
        private static extern bool WriteMsgQueue(IntPtr hMsgQ, byte[] lpBuffer, int cbDataSize, int dwTimeout, int dwFlags);

        [DllImport("coredll.dll", SetLastError = true)]
        private static extern bool CloseMsgQueue(IntPtr hMsgQ);

        [DllImport("coredll.dll", SetLastError = true)]
        public static extern Int32 WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);


        private struct MsgQueueInfo
        {
            public Int32 dwSize;
            public Int32 dwFlags;
            public Int32 dwMaxMessages;
            public Int32 cbMaxMessage;
            public Int32 dwCurrentMessages;
            public Int32 dwMaxQueueMessages;
            public Int16 wNumReaders;
            public Int16 wNumWriters;
        }

        private class MsgQueueOptions
        {
            private const int MSGQUEUE_ALLOW_BROKEN = 0x02;
            public MsgQueueOptions(bool readAccess, int maxMessageCount, int maxMessageSize)
            {
                dwSize = Marshal.SizeOf(typeof(MsgQueueOptions));
                dwFlags = MSGQUEUE_ALLOW_BROKEN;
                dwMaxMessages = maxMessageCount;
                cbMaxMessage = maxMessageSize;
                bReadAccess = Convert.ToInt32(readAccess);
            }
            public int dwSize;
            public int dwFlags;
            public int dwMaxMessages;
            public int cbMaxMessage;
            public int bReadAccess;
        }
        #endregion

        #region Dispose pattern
        private bool disposed = false;

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                }

                if (queueHandle != INVALID_HANDLE_VALUE)
                {
                    Close();
                }
            }

            disposed = true;
        }

        ~PointToPointMessageQueue()
        {
            Dispose(false);
        }
        #endregion
    }

    public class PointToPointMessageQueueFactory : IPointToPointMessageQueueFactory
    {
        private static PointToPointMessageQueueFactory instance = new PointToPointMessageQueueFactory();

        private PointToPointMessageQueueFactory()
        {
        }

        public static PointToPointMessageQueueFactory Instance
        {
            get
            {
                return instance;
            }
        }

        #region PointToPointMessageQueueFactory Members

        public IPointToPointMessageQueue Create()
        {
            return new PointToPointMessageQueue();
        }

        #endregion
    }
}